<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>微服务架构下的文件上传下载实践</title>
    <link rel="stylesheet" href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css">
    <link rel="stylesheet" href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        body {
            font-family: 'Noto Sans SC', Tahoma, Arial, Roboto, "Droid Sans", "Helvetica Neue", "Droid Sans Fallback", "Heiti SC", "Hiragino Sans GB", Simsun, sans-serif;
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            min-height: 100vh;
        }
        .hero-gradient {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        }
        .code-block {
            background: #1e1e1e;
            border-radius: 12px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            position: relative;
            overflow: hidden;
        }
        .code-block::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 40px;
            background: #2d2d2d;
            border-bottom: 1px solid #3e3e3e;
        }
        .code-dots {
            position: absolute;
            top: 14px;
            left: 20px;
            display: flex;
            gap: 8px;
        }
        .code-dot {
            width: 12px;
            height: 12px;
            border-radius: 50%;
        }
        .dot-red { background: #ff5f56; }
        .dot-yellow { background: #ffbd2e; }
        .dot-green { background: #27c93f; }
        pre {
            margin: 0;
            padding: 60px 30px 30px;
            overflow-x: auto;
            color: #d4d4d4;
            font-size: 14px;
            line-height: 1.6;
        }
        .section-card {
            background: white;
            border-radius: 16px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.08);
            transition: all 0.3s ease;
        }
        .section-card:hover {
            transform: translateY(-4px);
            box-shadow: 0 8px 30px rgba(0,0,0,0.12);
        }
        .step-number {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            width: 40px;
            height: 40px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
            font-size: 18px;
        }
        .mermaid {
            background: white;
            padding: 30px;
            border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.08);
        }
        .feature-icon {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            font-size: 48px;
        }
        .highlight-text {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            font-weight: 600;
        }
    </style>
</head>
<body>
    <!-- Hero Section -->
    <div class="hero-gradient text-white">
        <div class="container mx-auto px-6 py-20">
            <div class="max-w-4xl mx-auto text-center">
                <h1 class="text-5xl font-bold mb-6 leading-tight">
                    微服务架构下的<br>文件上传下载实践
                </h1>
                <p class="text-xl opacity-90 mb-8 leading-relaxed">
                    从浏览器到服务间通信，探索现代化文件处理方案
                </p>
                <div class="flex justify-center gap-6 text-sm">
                    <span class="flex items-center gap-2">
                        <i class="fas fa-cloud-upload-alt"></i>
                        七牛云存储
                    </span>
                    <span class="flex items-center gap-2">
                        <i class="fas fa-server"></i>
                        Spring Cloud
                    </span>
                    <span class="flex items-center gap-2">
                        <i class="fas fa-code"></i>
                        Feign客户端
                    </span>
                </div>
            </div>
        </div>
    </div>

    <!-- Main Content -->
    <div class="container mx-auto px-6 py-12 -mt-10">
        <!-- Introduction Card -->
        <div class="section-card p-8 mb-12 max-w-4xl mx-auto">
            <div class="flex items-start gap-4">
                <i class="fas fa-lightbulb feature-icon mt-2"></i>
                <div>
                    <h2 class="text-2xl font-bold mb-4 text-gray-800">核心概念</h2>
                    <p class="text-gray-600 leading-relaxed text-lg">
                        在微服务时代，文件的上传和下载不再局限于浏览器与服务器之间的交互。服务与服务之间的文件传输成为了新的挑战。本文将通过一个完整的示例，展示如何使用 Spring Cloud 和七牛云存储实现服务间的文件上传下载功能。
                    </p>
                </div>
            </div>
        </div>

        <!-- Architecture Diagram -->
        <div class="mb-12 max-w-5xl mx-auto">
            <h2 class="text-3xl font-bold text-center mb-8 text-gray-800">系统架构图</h2>
            <div class="mermaid">
                graph LR
                    A[客户端服务] -->|Feign调用| B[文件服务]
                    B -->|上传| C[七牛云存储]
                    C -->|返回URL| B
                    B -->|返回URL| A
                    A -->|下载请求| B
                    B -->|获取文件| C
                    C -->|返回文件流| B
                    B -->|返回byte[]| A
                    
                    style A fill:#667eea,stroke:#fff,stroke-width:2px,color:#fff
                    style B fill:#764ba2,stroke:#fff,stroke-width:2px,color:#fff
                    style C fill:#f093fb,stroke:#fff,stroke-width:2px,color:#fff
            </div>
        </div>

        <!-- Implementation Steps -->
        <div class="max-w-5xl mx-auto">
            <h2 class="text-3xl font-bold text-center mb-12 text-gray-800">实现步骤</h2>
            
            <!-- Step 1: SDK Import -->
            <div class="section-card p-8 mb-8">
                <div class="flex items-start gap-6">
                    <div class="step-number">1</div>
                    <div class="flex-1">
                        <h3 class="text-xl font-bold mb-4 text-gray-800">导入七牛云SDK</h3>
                        <p class="text-gray-600 mb-4">首先需要在项目中引入七牛云的Java SDK依赖</p>
                        <div class="code-block">
                            <div class="code-dots">
                                <div class="code-dot dot-red"></div>
                                <div class="code-dot dot-yellow"></div>
                                <div class="code-dot dot-green"></div>
                            </div>
                            <pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;com.qiniu&lt;/groupId&gt;
    &lt;artifactId&gt;qiniu-java-sdk&lt;/artifactId&gt;
    &lt;version&gt;7.13.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Step 2: DTO Definition -->
            <div class="section-card p-8 mb-8">
                <div class="flex items-start gap-6">
                    <div class="step-number">2</div>
                    <div class="flex-1">
                        <h3 class="text-xl font-bold mb-4 text-gray-800">定义文件传输DTO</h3>
                        <p class="text-gray-600 mb-4">创建用于服务间文件传输的数据传输对象</p>
                        <div class="code-block">
                            <div class="code-dots">
                                <div class="code-dot dot-red"></div>
                                <div class="code-dot dot-yellow"></div>
                                <div class="code-dot dot-green"></div>
                            </div>
                            <pre><code class="language-java">// 接口文件上传的dto
@Data
public class ApiFileDto {

    private byte[] file;     // 文件byte数组
    
    private String fileName; // 文件名称
}</code></pre>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Step 3: Qiniu Configuration -->
            <div class="section-card p-8 mb-8">
                <div class="flex items-start gap-6">
                    <div class="step-number">3</div>
                    <div class="flex-1">
                        <h3 class="text-xl font-bold mb-4 text-gray-800">配置七牛云上传信息</h3>
                        <p class="text-gray-600 mb-4">配置七牛云的认证信息和上传管理器</p>
                        <div class="code-block">
                            <div class="code-dots">
                                <div class="code-dot dot-red"></div>
                                <div class="code-dot dot-yellow"></div>
                                <div class="code-dot dot-green"></div>
                            </div>
                            <pre><code class="language-java">@Configuration
public class QiniuConfig {

    @Value("${qiniu.ak}")
    private String ak;

    @Value("${qiniu.sk}")
    private String sk;

    // 上传管理对象
    @Bean
    public UploadManager uploadManager() {
        UploadManager uploadManager = new UploadManager(new com.qiniu.storage.Configuration());
        return uploadManager;
    }

    // 认证对象
    @Bean
    public Auth qiqiuAuth() {
        return Auth.create(ak, sk);
    }
}</code></pre>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Step 4: File Controller -->
            <div class="section-card p-8 mb-8">
                <div class="flex items-start gap-6">
                    <div class="step-number">4</div>
                    <div class="flex-1">
                        <h3 class="text-xl font-bold mb-4 text-gray-800">实现文件上传下载接口</h3>
                        <p class="text-gray-600 mb-4">创建支持浏览器上传和API上传的控制器</p>
                        <div class="code-block">
                            <div class="code-dots">
                                <div class="code-dot dot-red"></div>
                                <div class="code-dot dot-yellow"></div>
                                <div class="code-dot dot-green"></div>
                            </div>
                            <pre><code class="language-java">@RestController
@RequestMapping("/file")
@Slf4j
public class FileController {

    @Autowired
    private UploadManager uploadManager;

    @Autowired
    private Auth auth;

    @Value("${qiniu.bucket}")
    private String bucket;

    @Value("${qiniu.host}")
    private String host;

    // 浏览器上传
    @PostMapping("/upload")
    public R fileUpload(MultipartFile file) {
        // 1.获取token
        String token = auth.uploadToken(bucket);
        try {
            // 2.上传
            Response response = uploadManager.put(file.getBytes(), file.getOriginalFilename(), token);
            return R.ok().put("data", host + file.getOriginalFilename());
        } catch (IOException e) {
            log.error("上传文件失败", e);
            return R.error("文件上传失败");
        }
    }

    // 接口上传，上传成功后返回文件地址
    @PostMapping("/apiUpload")
    public String apiFileUpload(@RequestBody ApiFile apiFile) {
        System.out.println("apiFile = " + apiFile);
        // 1.获取token
        String token = auth.uploadToken(bucket);
        try {
            // 2.上传
            Response response = uploadManager.put(apiFile.getFile(), apiFile.getFileName(), token);
            return host + apiFile.getFileName();
        } catch (IOException e) {
            log.error("上传文件失败", e);
        }
        return null;
    }

    // 接口下载
    @PostMapping("/apiDownload")
    public byte[] apiDownload(String url) {
        InputStream ips = null;
        try {
            HttpResponse response = HttpUtils.doGet(url);
            ips = response.getEntity().getContent();
            return IOUtils.toByteArray(ips);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ips != null) {
                try {
                    ips.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}</code></pre>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Step 5: Feign Client -->
            <div class="section-card p-8 mb-8">
                <div class="flex items-start gap-6">
                    <div class="step-number">5</div>
                    